package jmine.tec.web.wicket.pages.form;

import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_CANNOT_FIND_ENTITY_TO_DELETE;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_CANNOT_FIND_ENTITY_TO_EDIT;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_CANNOT_FIND_ENTITY_TO_VIEW;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.INFO_ENTITY_DELETE_SUCCESS;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import jmine.tec.persist.PersistMessages;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;
import jmine.tec.web.wicket.component.command.button.ButtonCommand;
import jmine.tec.web.wicket.component.command.button.NewCommand;
import jmine.tec.web.wicket.component.command.button.NewCommandHandler;
import jmine.tec.web.wicket.component.command.img.DeleteCommand;
import jmine.tec.web.wicket.component.command.img.DeleteCommandHandler;
import jmine.tec.web.wicket.component.command.img.EditCommand;
import jmine.tec.web.wicket.component.command.img.EditCommandHandler;
import jmine.tec.web.wicket.component.command.img.ImageCommand;
import jmine.tec.web.wicket.component.command.img.ViewCommand;
import jmine.tec.web.wicket.component.command.img.ViewCommandHandler;
import jmine.tec.web.wicket.exception.ExceptionHandler;
import jmine.tec.web.wicket.pages.HistoryAwarePage;
import jmine.tec.web.wicket.spring.SpringBeanReference;

import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.persist.exception.PersistenceException;

/**
 * Implementação base de uma página principal de Crud.
 * 
 * @author Rafael Volpato
 * @date Aug 8, 2011
 * @param <BO> Tipo da entidade da página de pesquisa
 */
public abstract class CrudPage<BO extends Persistable> extends ListPage<BO, BO> implements DeleteCommandHandler, EditCommandHandler,
        ViewCommandHandler, NewCommandHandler {

    /**
     * Construtor.
     */
    public CrudPage() {
        super();
    }

    /**
     * Construtor.
     * 
     * @param pageParameters {@link PageParameters}
     */
    public CrudPage(PageParameters pageParameters) {
        super(pageParameters);
    }

    /**
     * Retorna a classe da entidade que esta sendo manipulada por esta página.
     * 
     * @return a classe da entidade que esta sendo manipulada por esta página
     */
    @Override
    @SuppressWarnings("unchecked")
    public Class<BO> getEntityClass() {
        try {
            return (Class<BO>) GenericTypeResolverImpl.getInstance().resolveTypeForParameterizedType(
                    (ParameterizedType) this.getClass().getGenericSuperclass(), this.getClass(), 0);
        } catch (IllegalArgumentException e) {
            throw new PersistenceException(PersistMessages.INVALID_BEAN_IMPLEMENTATION.create(this.getClass().getName()), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected final BO createModel() {
        BaseDAO<BO> dao = this.daoFactoryRef.get().getGenericDAO(this.getEntityClass());
        BO entity = dao.createBean();
        this.fillSearchEntity(entity);
        return entity;
    }

    /**
     * Exclui a entidade de PK informado.
     * 
     * @param entityPK pk da entidade que deve ser excluida.
     * @return <code>null</code>
     */
    public final Page createDeletePage(Serializable entityPK) {
        BO entity = null;
        try {
            entity = this.loadEntity(entityPK);
        } catch (BeanNotFoundException e) {
            this.error(ERROR_CANNOT_FIND_ENTITY_TO_DELETE.create(this.getPageName(), entityPK));
            return null;
        }

        try {
            entity.getPersister().remove();
        } catch (Exception e) {
            SpringBeanReference.<ExceptionHandler> forName("wicketExceptionHandler").get().handle(e);
            return null;
        }

        this.info(INFO_ENTITY_DELETE_SUCCESS.create().getMessage());
        return null;
    }

    /**
     * Redireciona para a página de edição da entidade de PK informado.
     * 
     * @param entityPK pk da entidade que deve ser editada.
     * @return página para onde deve ser direcionado para editar a entidade.
     */
    public final Page createEditPage(Serializable entityPK) {
        try {
            Page thePage = this.createFormPage(this.loadEntity(entityPK), FormType.EDIT);
            if(thePage instanceof HistoryAwarePage) {
                ((HistoryAwarePage) thePage).setBackToPageInstace(this);
            }
            return thePage;
        } catch (BeanNotFoundException e) {
            this.error(ERROR_CANNOT_FIND_ENTITY_TO_EDIT.create(this.getPageName(), entityPK));
            return null;
        }
    }

    /**
     * Redireciona para a página de visualização da entidade de PK informado.
     * 
     * @param entityPK pk da entidade que deve ser exibida.
     * @return página para onde deve ser direcionado para visualizar a entidade.
     */
    public final Page createViewPage(Serializable entityPK) {
        try {
            Page thePage = this.createFormPage(this.loadEntity(entityPK), FormType.VIEW);
            if(thePage instanceof HistoryAwarePage) {
                ((HistoryAwarePage) thePage).setBackToPageInstace(this);
            }
            return thePage;
        } catch (BeanNotFoundException e) {
            this.error(ERROR_CANNOT_FIND_ENTITY_TO_VIEW.create(this.getPageName(), entityPK));
            return null;
        }
    }

    /**
     * Hook para preencher a entidade de pesquisa.
     * 
     * @param entity entity
     */
    protected void fillSearchEntity(BO entity) {
        // Hook
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @return lista de comandos que devem ser exibidos
     */
    @Override
    protected List<ButtonCommand> getPageCommands() {
        List<ButtonCommand> commands = super.getPageCommands();
        commands.add(new NewCommand(this));
        return commands;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean shouldIncludeTableCommands() {
        return true;
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @param entity entity
     * @return lista de comandos que devem ser exibidos
     */
    @Override
    protected List<ImageCommand> getTableCommands(BO entity) {
        List<ImageCommand> commands = super.getTableCommands(entity);
        commands.add(new ViewCommand(this));
        commands.add(new EditCommand(this));
        commands.add(new DeleteCommand(this));
        return commands;
    }

    /**
     * Retorna a página para a visualização/edição de uma entidade.
     * 
     * @param entity entidade que deve ser emixibida/editada
     * @param formType tipo do formulario (visualização/edição)
     * @return a página para a visualização/edição de uma entidade
     */
    protected abstract Page createFormPage(BO entity, FormType formType);
}
